/*
 * Copyright 2010-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.code.newpath.core.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.code.newpath.core.Context;
import com.google.code.newpath.core.Path;
import com.google.code.newpath.core.PathMatchPattern;
import com.google.code.newpath.core.Resource;
import com.google.code.newpath.core.ResourceFactory;
import com.google.code.newpath.core.exception.PathException;
import com.google.code.newpath.core.exception.ResourceInstanceException;

/**
 * Basic method implements for Conext.
 * In its lifecycle scope, it will cache each target instance which initialized in getTarget(...) method. 
 * 
 * @author Charlie Zhang
 *
 */
public class BaseContext implements Context {
	
	private Context parentContext;
	
	private String scope;
	
	private ResourceFactory resourceFactory;
	
	private Path workingDirectory;
	
	private Map<Path, Object> targetCache;

	public BaseContext(Context parentContext, String scope, ResourceFactory resourceFactory) {
		this.parentContext = parentContext;
		this.scope = scope;
		this.resourceFactory = resourceFactory;
		this.targetCache = new HashMap<Path, Object>();
	}

	public Context getParent() {
		return parentContext;
	}

	public ResourceFactory getResourceFactory() {
		return resourceFactory;
	}

	public String getScope() {
		return scope;
	}

	public synchronized final Object getTarget(Path path, boolean setWorkingDirectory, boolean thisScopeOnly) {
				
		Object target = this.targetCache.get(path);
		if(target == null) {

			Path fullPath;
			if(path.isAbsolute() == false) {
				fullPath = new Path(workingDirectory, path.getFullPath());
			}
			else {
				fullPath = path;
			}
			
			Resource<?> resource = resourceFactory.getResource(fullPath);
			
			if(resource != null) { 
				if(scope.equals(resource.getScope())) {
					target = resource.instanceTarget(this);
					
					//Only cache this scope target
					this.targetCache.put(path, target);
				}
				else if(thisScopeOnly == false){
					if(parentContext != null) {
						target = parentContext.getTarget(fullPath, setWorkingDirectory, thisScopeOnly);
					}
					else {
						throw new ResourceInstanceException("Failed to find the match context for scope=" + scope + ")", resource);
					}
				}
			}
		}
		
		if(setWorkingDirectory) {
			Path parent = path.getParent();
			if(parent != null && (!parent.equals(workingDirectory))) {
				this.workingDirectory = parent;
			}
		}
		
		return target;
	}

	public Object getTarget(Path path) {
		return getTarget(path, true, false);
	}
	
	public Object getTarget(String path, boolean setWorkingDirectory, boolean thisScope) {
		return getTarget(new Path(path), setWorkingDirectory, thisScope);
	}

	public Object getTarget(String path) {
		return getTarget(new Path(path));
	}

	public synchronized Path getWorkingDirectory() {
		return workingDirectory;
	}

	public synchronized void setWorkingDirectory(Path path) throws PathException {
		this.workingDirectory = path;
	}
	
	public synchronized void flush(PathMatchPattern mathPattern) {
		
		Collection<Path> removingKeys = new ArrayList<Path>();
		
		//To avoid the ConcurrentModificationException, we should do removing operations by following two step:
		//1 Find the match removing keys;
		Set<Path> keys = targetCache.keySet();
		removingKeys = new ArrayList<Path>();
		if(mathPattern != null) {
			for(Path key : keys) {
				if(mathPattern.isMatch(key)) {
					removingKeys.add(key);
				}
			}
		}
		else {
			for(Path key : keys) {
				removingKeys.add(key);
			}
		}


		//2.Actually remove the values from targetCache
		if(removingKeys.size() > 0) {
			for(Path removingKey : removingKeys) {
				Resource resource = getResource(removingKey);
				resource.destroyTarget(targetCache.get(removingKey));
				
				targetCache.remove(removingKey);
			}
		}	
	}
	
	
	protected Resource<?> getResource(Path path) {
		Path fullPath;
		if(path.isAbsolute() == false) {
			fullPath = new Path(workingDirectory, path.getFullPath());
		}
		else {
			fullPath = path;
		}
		
		return resourceFactory.getResource(fullPath);
	}

}
